/**
 *  Copyright (C) 2007 Marcos Diez < marcos AT unitron.com.br >
 *  Copyright (C) 2007 PGT-Linux.org http://www.pgt-linux.org
 *  Author: vandorpe Olivier <vandorpeo@pgt-linux.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <usb.h>
#include <syslog.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define telbox_idVendor 0x6993
#define telbox_idProduct 0xb001

//Interface
#define AUDIO_IN        0x01
#define AUDIO_OUT       0x02
#define CONTROL         0x03

#define USB_MSG_IN      USB_TYPE_CLASS
#define USB_MSG_OUT     USB_TYPE_CLASS | 0x01
#define TIME_OUT        1600

#include "usbb2k-api.h"
#include "usbTalker.h"
#include "yealinkProtocol.h"

// if this gets to big, I will just assume the device has been removed.
int usbContinuousBusErrorCounter=0;

static pthread_mutex_t mutexUsbTalker= PTHREAD_MUTEX_INITIALIZER;

void usbTalkerInit() {
	pthread_mutex_init( &mutexUsbTalker, 0);
	usb_init();
}

int usb_talk(usb_dev_handle *dev_h, unsigned char *inputData, char *outputData) {
	if ( !dev_h) {
		return USB_TALK_INVALID_MSG;
	}
	pthread_mutex_lock( &mutexUsbTalker);

	int err;
	// usleep(25000);

	err = usb_control_msg(dev_h, USB_MSG_OUT, 0x9, 0x200, 0x03,
			( char *) inputData, 
			URB_LENGTH, TIME_OUT);
	if (err < 0) {
		usbContinuousBusErrorCounter++;
	} else {
		usbContinuousBusErrorCounter=0;
	}
	if (err != URB_LENGTH) {
		pthread_mutex_unlock( &mutexUsbTalker);
		return USB_TALK_INVALID_MSG;
	}

	if (outputData == NULL) {
		pthread_mutex_unlock( &mutexUsbTalker);
		return USB_TALK_OK;
	}

	err = usb_interrupt_read(dev_h, 0x81, ( char *) outputData, URB_LENGTH, 
	TIME_OUT);
	if (err < 0) {
		usbContinuousBusErrorCounter++;
	} else {
		usbContinuousBusErrorCounter=0;
	}
	if (err!=URB_LENGTH) {
		pthread_mutex_unlock( &mutexUsbTalker);
		return USB_TALK_INVALID_READ;
	}

	pthread_mutex_unlock( &mutexUsbTalker);
	return USB_TALK_OK;
}

int usb_talk_debug(const char *caller, usb_dev_handle *dev_h,
		unsigned char *inputData, char *outputData) {

	usb_log(caller, ':', inputData);
	int err = usb_talk(dev_h, inputData, outputData);
	usb_log(caller, '!', (unsigned char *) outputData);

	return err;

}

void usb_log(const char *caller, char iconChar, const unsigned char *debugData) {
	const char
			*msg=
					"%s%c%.2x %.2x %.2x %.2x [ %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x ] %.2x";

	syslog(LOG_INFO,
	msg ,
	caller , iconChar , debugData[0] ,
	debugData[1],debugData[2],debugData[3],
	debugData[4],debugData[5],debugData[6],
	debugData[7],debugData[8],debugData[9],
	debugData[10],debugData[11],debugData[12],
	debugData[13],debugData[14],debugData[15]);

}

int usbb2k_init(struct usb_device* dev, yealinkDevice_t *yealinkDevice) {
	// usb_dev_handle *
	//struct usb_device* dev, int *deviceVersion,
	//}
	//	unsigned char *serialNumber, char **name) {
	int err;

	usb_dev_handle *dev_h;

	if (!(dev_h=usb_open(dev))) {
		syslog(LOG_ERR,"USB interface not opened");
		return YEALINK_DEVICE_NOT_OPENED;
	}
	err=usb_claim_interface(dev_h, CONTROL);
	if (err != 0) {
#ifdef   LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
		usb_detach_kernel_driver_np(dev_h, CONTROL);
		err=usb_claim_interface(dev_h, CONTROL);
#endif 
		if (err !=0) {
			usb_close(dev_h);
			syslog(LOG_ERR,"USB claim interface failed. Are you root ?");
			return YEALINK_DEVICE_NOT_CLAIMED;
		}
	}

	yealinkDevice->dev_h=dev_h;
	/* USB PROTOCOL INIT */
	int result = yealinkInit(yealinkDevice);
	if (result != YEALINK_DEVICE_SUCESSIFULLY_INITIALIZED) {
		syslog(LOG_ERR,"ERROR initializing Yealink device");
		releaseDevice(yealinkDevice );
	}

	return result;
}

int claimYealinkDeviceV(yealinkDevice_t *yealinkDevice, int busNumber,
		int deviceNumber, void (*initializer)(yealinkDevice_t *yealinkDevice)) {

	//searches and initializes one yealink device.
	//if busNum == SEARCH_IN_EVERY_BUS, tries to initialize a device at random
	//else, the device on bus busNum

	//if it finds and claims, returns YEALINK_DEVICE_SUCESSIFULLY_INITIALIZED
	//if it does not, returns YEALINK_DEVICE_NOT_FOUND
	//else, returns whichever status of the last detected yealink device.

	// this function will never search a device more than once, 


	// Else, I search only on busNum. 
	// Somehow, bus->dirname is a string.... that is doff
	//  // I should catch the right bus with
	//  // whatever.. I will autodetect and get the first avaliable one....
	//  /*   int theBus=-1; */
	//  /*   char *theBusString = getenv( "HAL_PROP_USB_BUS_NUMBER" ); */
	//  /*   if( theBusString ){ */
	//  /*     sscanf( theBusString , "%d" ,  &theBus ); */
	//  /*   } */
	struct usb_bus *bus;
	struct usb_device *device;

	usb_find_busses();
	usb_find_devices();

	for (bus=usb_busses; bus; bus=bus->next) {
		if (busNumber != SEARCH_IN_EVERY_BUS) {
			int thisBusNum;
			int gotAvalue = sscanf(bus->dirname, "%d", &thisBusNum);
			if ( !gotAvalue)
				continue;
			if (thisBusNum != busNumber)
				continue;
		}
		// if I am here, either busNum == SEARCH_IN_EVERY_BUS or busNum == atoi(bus->dirname)
		for (device = bus->devices; device; device=device->next) {
			if (deviceNumber != PICK_ANY_DEVICE && deviceNumber
					!= device->devnum) {
				continue;
			}

			if ((device->descriptor.idVendor == telbox_idVendor)
					&& (device->descriptor.idProduct ==telbox_idProduct)) {
				syslog(LOG_INFO, "Yealink device found on bus %s" , bus->dirname);

				int numTries= (initializer == 0 ) ? 3 : 1;
				sscanf(bus->dirname, "%d", &(yealinkDevice->usbBusNumber));
				yealinkDevice->usbDeviceNumber=device->devnum;
				while (numTries-- > 0) {

					int result = usbb2k_init(device, yealinkDevice);

					if (result == YEALINK_DEVICE_SUCESSIFULLY_INITIALIZED) {
						if (initializer == 0) {
							return result;
						} else {
							initializer(yealinkDevice);
						}
					} else { // result != YEALINK_DEVICE_SUCESSIFULLY_INITIALIZED 
						if (initializer != 0) {
							char *errorMsg=0;
							switch (result) {

							case YEALINK_DEVICE_NOT_FOUND:
								errorMsg="found";
								break;
							case YEALINK_DEVICE_NOT_OPENED:
								errorMsg="opened";
								break;
							case YEALINK_DEVICE_NOT_CLAIMED:
								errorMsg="claimed (are you root?)";
								break;
							case YEALINK_DEVICE_NOT_B2KB3G:
								errorMsg="supported (invalid device)";
								break;
							case YEALINK_DEVICE_NOT_INITIALIZED:
								errorMsg="initialized";
								break;
							default:
								errorMsg="reconized";
								break;
							}
							if (errorMsg) {
								printf(
										"USB bus %s/%s: Yealink device found but not %s.\n",
										bus->dirname, device->filename,
										errorMsg);
							}

						}
					}
					if (initializer == 0) {
						sleep(3);
					}
				}
				// else, keep searching for it
			}
		}
	}
	return YEALINK_DEVICE_NOT_FOUND;
}

void releaseDevice(yealinkDevice_t *yealinkDevice) {
	if( yealinkDevice->dev_h){
		usb_release_interface(yealinkDevice->dev_h, CONTROL);
		// usb_reset(client_arg.dev_h); // if I reset I don't have to close
		usb_close(yealinkDevice->dev_h);
		yealinkDevice->dev_h = 0;
	}
}
